package com.wcs.exam.common.log;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.wcs.exam.common.base.annotation.OptLog;
import com.wcs.exam.common.cache.CacheRedis;
import com.wcs.exam.common.config.ThreadContext;
import com.wcs.exam.common.util.IpUtil;
import com.wcs.exam.common.util.JsonUtil;
import com.wcs.exam.dao.SysLogDao;
import com.wcs.exam.dao.impl.mapper.entity.SysLog;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@RequiredArgsConstructor
@Slf4j
@Aspect
@Component
public class AspectOptLog {

    @NotNull
    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @NotNull
    private final CacheRedis cacheRedis;
    @NotNull
    private final SysLogDao sysLogDao;

    @Pointcut("@annotation(com.wcs.exam.common.base.annotation.OptLog)")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        try {
            Object[] args = joinPoint.getArgs();

            // 获取请求路径
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            String path = request.getServletPath();

            // 用户ID
            long userId = ThreadContext.getUserId();

            // 请求方式
            String method = request.getMethod();

            // 获取IP地址
            String ipAddress = IpUtil.getIpAddress(request);

            // 处理操作内容
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            OptLog optLog = signature.getMethod().getAnnotation(OptLog.class);
            if (ObjectUtil.isNull(optLog)) {
                return;
            }

            // 用户操作
            String operation = optLog.value();
            String userAgentStr = request.getHeader("user-agent");
            threadPoolTaskExecutor.execute(() -> {
                // 操作内容
                String argsStr = JsonUtil.toJsonString(args);

                // 获取字段描述
                Map<String, String> fieldDescMap = new HashMap<>();
                for (Object arg : args) {
                    // 遍历参数类的所有声明字段
                    for (Field field : arg.getClass().getDeclaredFields()) {
                        if (field.isAnnotationPresent(Schema.class)) {
                            Schema schema = field.getAnnotation(Schema.class);
                            if (ObjectUtil.isNotNull(schema)) {
                                fieldDescMap.put(field.getName(), schema.description());
                            }
                        }
                    }
                }

                // 获取操作内容
                String content = getContent(argsStr, optLog, userId, fieldDescMap);

                SysLog sysLog = new SysLog()
                        .setUserId(userId)
                        .setOperation(operation)
                        .setMethod(method)
                        .setPath(path)
                        .setContent(content)
                        .setLoginIp(ipAddress);

                // IP地址
                IpUtil.IpInfo ipInfo = IpUtil.getIpInfo(ipAddress);
                if (ObjectUtil.isNotNull(ipInfo)) {
                    sysLog.setCountry(ipInfo.getRegion());
                    sysLog.setProvince(ipInfo.getPro());
                    sysLog.setCity(ipInfo.getCity());
                }

                // 浏览器
                UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
                if (ObjectUtil.isNotNull(userAgent)) {
                    sysLog.setBrowser(userAgent.getBrowser().getName());
                    sysLog.setOs(userAgent.getOs().getName());
                }

                // 保存
                sysLogDao.save(sysLog);
            });
        } catch (Exception e) {
            log.warn("保存操作日志失败！", e);
        }
    }

    private String getContent(String argsStr, OptLog optLog, long userId, Map<String, String> fieldDescMap) {
        String content = "";
        if (StringUtils.hasText(argsStr)) {
            if (optLog.isUpdate()) {
                // 如果是修改或者编辑，比对变化
                // 修改后的值
                Map<String, Object> updateMap = JSONUtil.parseArray(argsStr).getJSONObject(0);
                String cacheKey = optLog.key() + userId + updateMap.get("id");
                if (cacheRedis.hasKey(cacheKey)) {
                    // 修改前的值
                    Map<String, Object> originalMap = JSONUtil.parseObj(cacheKey);
                    // 及时删除缓存
                    cacheRedis.delete(cacheKey);

                    // 进行对比
                    content = getUpdateContent(originalMap, updateMap, fieldDescMap);
                } else {
                    content = getSaveContent(argsStr, fieldDescMap);
                }
            } else {
                content = getSaveContent(argsStr, fieldDescMap);
            }
        }
        return content;
    }

    private static String getSaveContent(String argsStr, Map<String, String> fieldDescMap) {
        String content;
        JSONObject contentJson = new JSONObject();

        Map<String, Object> updateMap = JSONUtil.parseArray(argsStr).getJSONObject(0);
        for (Map.Entry<String, Object> entry : updateMap.entrySet()) {
            String fieldDesc = fieldDescMap.getOrDefault(entry.getKey(), "");

            JSONObject valueJson = new JSONObject();
            valueJson.set("desc", fieldDesc);
            valueJson.set("value", entry.getValue());
            contentJson.set(entry.getKey(), valueJson);
        }
        content = contentJson.toString();
        return content;
    }

    /**
     * 对比修改前后的值
     *
     * @param originalMap  修改前的值
     * @param updateMap    修改后的值
     * @param fieldDescMap 字段描述
     * @return 修改对比结果
     */
    private static String getUpdateContent(Map<String, Object> originalMap, Map<String, Object> updateMap, Map<String, String> fieldDescMap) {
        JSONObject changeJson = new JSONObject();
        for (Map.Entry<String, Object> entry : originalMap.entrySet()) {
            String originalValue = entry.getValue().toString();
            String updateValue = updateMap.get(entry.getKey()).toString();

            // 比较更新的字段
            if (!originalValue.equals(updateValue)) {
                JSONObject valueJson = new JSONObject();
                valueJson.set("original", originalValue);
                valueJson.set("update", updateValue);
                valueJson.set("desc", fieldDescMap.getOrDefault(entry.getKey(), ""));
                changeJson.set(entry.getKey(), valueJson);
            }
        }

        // 处理新增字段
        JSONObject newFieldJson = new JSONObject();
        for (Map.Entry<String, Object> entry : updateMap.entrySet()) {
            if (originalMap.containsKey(entry.getKey())) {
                continue;
            }

            newFieldJson.set(entry.getKey(), entry.getValue());
            newFieldJson.set("desc", fieldDescMap.getOrDefault(entry.getKey(), ""));
        }
        if (!newFieldJson.isEmpty()) {
            changeJson.set("newField", newFieldJson);
        }
        return changeJson.toString();
    }
}
